/*
 * Copyright 2010 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package java.io;

import com.google.gwt.corp.localstorage.LocalStorage;

/**
 * Saves binary data to the local storage; currently using hex encoding.
 * The string is prefixed with "hex:"
 * @author haustein
 */
public class RandomAccessFile /*implements DataOutput, DataInput, Closeable*/ {

/*
    public final FileDescriptor getFD() throws IOException { }
    public final FileChannel getChannel() { }
*/
	final String name;
	final boolean writeable;
	boolean dirty;
	String data;
	int newDataPos;
	StringBuilder newData;
	int pos;
	int len;
	DataInputStream dis = new DataInputStream(new RafInputStream());
	DataOutputStream dos = new DataOutputStream(new RafOutputStream());
	
    public RandomAccessFile(String name, String mode) throws FileNotFoundException { 
    	this (new File(name), mode);
    }

    public RandomAccessFile(File file, String mode) throws FileNotFoundException { 
    	name = file.getCanonicalPath();
    	
    	mode = mode.toLowerCase();
    	if (!mode.equals("r") && !mode.equals("rw")) {
    		throw new IllegalArgumentException("mode: '" + mode + "'");
    	} 
    	writeable = mode.equals("rw");
    	if (file.exists()) {
    		try {
    			data = atob(LocalStorage.getItem(name));
    			len = data.length();
    		} catch (IOException e) {
    			throw new FileNotFoundException("" + e);
    		}
    	} else if (writeable) {
    		data = "";
    		dirty = true;
    		try {
    			flush();
    		} catch (IOException e) {
    			throw new FileNotFoundException("" + e);
    		}
    	} else {
    		throw new FileNotFoundException(name);
    	}
    }

    static native String btoa(String s) /*-{
    	return $wnd.btoa(s);
    }-*/;

    static native String atob(String s) /*-{
		return $wnd.atob(s);
	}-*/;

    public int read() throws IOException { 
    	return dis.read();
    }

    public int read(byte b[], int off, int len) throws IOException { 
    	return dis.read(b, off, len);
    }

    public int read(byte b[]) throws IOException { 
    	return dis.read(b); 
    }

    public final void readFully(byte b[]) throws IOException { 
    	dis.readFully(b);
    }

    public final void readFully(byte b[], int off, int len) throws IOException { 
    	dis.readFully(b, off, len);
    }

    public int skipBytes(int n) throws IOException { 
    	return dis.skipBytes(n); 
    }

    public void write(int b) throws IOException { 
    	dos.write(b);
    };

    public void write(byte b[]) throws IOException { 
    	dos.write(b);
    }

    public void write(byte b[], int off, int len) throws IOException {
    	dos.write(b, off, len);
    }

    public long getFilePointer() throws IOException { 
    	return pos;
    }

    public void seek(long pos) throws IOException { 
    	if (pos < 0) {
    		throw new IllegalArgumentException();
    	}
    	this.pos = (int) pos;
    }

    public long length() throws IOException {
    	return len;
    }
    
    public void setLength(long newLength) throws IOException {
    	if (len != newLength) {
    		consolidate();
    		if (data.length() > newLength) {
    			data = data.substring(0, (int) newLength);
    			len = (int) newLength;
    		} else {
//    			System.out.println("padding " + (newLength - len) + " zeros in setLength to " + newLength);
    			while (len < newLength) {
    				write(0);
    			}
    		}
    	}
    }

    public void close() throws IOException {
    	if (data != null) {
    		flush();
    		data = null;
    	}
    }

    private void consolidate() {
    	if (newData == null) {
    		return;
    	}
    	
//    	System.out.println("consolidate(); ndp: " + newDataPos + " nd-len: " + newData.length());
    	
    	if (data.length() < newDataPos) {
          StringBuilder filler = new StringBuilder();
    	  while (data.length() + filler.length() < newDataPos) {
    		filler.append('\0');
          }
//    	  System.out.println("padding " + (filler.length()) + " zeros in consolidate ");
    	  
    	  data += filler.toString();
    	}
    	
    	int p2 = newDataPos + newData.length();
    	data = data.substring(0, newDataPos) + newData.toString() +
    		(p2 < data.length() ? data.substring(p2) : "");
    	newData = null;
     }


    void flush() throws IOException {
    	if (!dirty) {
    		return;
    	}
    	consolidate();
    	LocalStorage.setItem(name, btoa(data));
    	dirty = false;
    }
    
    
    public final boolean readBoolean() throws IOException { 
    	return dis.readBoolean(); 
    }

    public final byte readByte() throws IOException { 
    	return dis.readByte(); 
    }

    public final int readUnsignedByte() throws IOException { 
    	return dis.readUnsignedByte(); 
    }

    public final short readShort() throws IOException { 
    	return dis.readShort(); 
    }

    public final int readUnsignedShort() throws IOException { 
    	return dis.readUnsignedShort(); 
    }

    public final char readChar() throws IOException { 
    	return dis.readChar(); 
    }

    public final int readInt() throws IOException { 
    	return dis.readInt(); 
    }

    public final long readLong() throws IOException { 
    	return dis.readLong(); 
    }

    public final float readFloat() throws IOException { 
    	return dis.readFloat(); 
    }

    public final double readDouble() throws IOException { 
    	return dis.readDouble(); 
    }

    public final String readLine() throws IOException { 
    	return dis.readLine();
    }

    public final String readUTF() throws IOException { 
    	return dis.readUTF(); 
    }

    public final void writeBoolean(boolean v) throws IOException {
    	dos.writeBoolean(v);
    }

    public final void writeByte(int v) throws IOException { 
    	dos.writeByte(v);
    }

    public final void writeShort(int v) throws IOException { 
    	dos.writeShort(v);
    }

    public final void writeChar(int v) throws IOException { 
    	dos.writeChar(v);
    }

    public final void writeInt(int v) throws IOException { 
    	dos.writeInt(v);
    }

    public final void writeLong(long v) throws IOException {
    	dos.writeLong(v);
    }

    public final void writeFloat(float v) throws IOException { 
    	dos.writeFloat(v);
    }

    public final void writeDouble(double v) throws IOException { 
    	dos.writeDouble(v);
    }

    public final void writeBytes(String s) throws IOException { 
    	dos.writeBytes(s);
    }

    public final void writeChars(String s) throws IOException { 
    	dos.writeChars(s);
    }

    public final void writeUTF(String str) throws IOException { 
    	dos.writeUTF(str);
    }
    
//    public final FileChannel getChannel() throws IOException { }
    
	class RafInputStream extends InputStream {
		@Override
		public int read() throws IOException {
			if (pos >= len) {
				return -1;
			} else {
				consolidate();
				return data.charAt(pos++);
//				int p2 = (pos << 1);
//				int result = Character.digit(data.charAt(p2), 16) * 16 + 
//  					Character.digit(data.charAt(p2 + 1), 16);
//				pos++;
//				return result;
			}
		}
	}
	
	class RafOutputStream extends OutputStream {	

		public void write(int b) throws IOException {
			if (!writeable) {
				throw new IOException("not writeable");
			}
			
			if (newData == null) {
				newDataPos = pos;
				newData = new StringBuilder();
//				System.out.println("no buf; newDataPos: " + pos);
			} else if (newDataPos + newData.length() != pos) {
				consolidate();
				newDataPos = pos;
				newData = new StringBuilder();
//				System.out.println("pos mismatch; newDataPos: " + pos);
			}

			newData.append((char) (b&255));
//			newData.append("" + Character.forDigit((b >> 4) & 15, 16) + 
//					Character.forDigit(b & 15, 16));
			pos++;
			len = Math.max(pos, len);
			dirty = true;
		}
	}
}
